方永、南天紫雲

道亦有道

dllcall ,用批处理做程序
2011年08月25日

dllcall ,用批处理做程序

dllcall 类似于 rundll32 的程序,但是调用很明了,实现方式也和 rundll32 不同,rundll32 可以为一些API提供窗口,dllcall 只是一个命令行(console pro)程序,所以 rundll32 应用最广的调用.cpl文件的执行在dllcall中不能实现。dllcall 主要用来扩展批处理,可以用批处理做一些简单的程序。

dllcall 的制作缘起主要是由于作者以前一直在用 NSIS 将一些工具打包放在移动设备中用来部署工作环境,但移动设备中的exe文件经常会被病毒感染,而且当工具包有一点小改动时也要重新编译整个包,当这个包很大时就很耗费时间,所以改用批处理加 7za 加单独压缩的包的形式,shorcut 也是这个原因制作的。

dllcall的特点:

  1. 使用汇编语言编写,fasm汇编器汇编,指令级优化,极小的体积(目前版本4KB),超快的速度,真正意义上的绿色软件。
  2. 开源软件,使用MIT开源协议发布,可以了解程序的每一个细节,放心地使用(可惜360之类的很多杀毒软件会报毒,汇编语言写的程序命运大都如此吧)。
  3. 提供的只是一种机制,而非功能,从而随心所欲地实现自己想要的功能,而且仅仅是通过批处理。
  4. 由于涉及到Windows API的调用,可能有些拗,其实编程很简单,很多人大抵是被课堂里的C语言给震慑了吧,用dllcall做程序,首先需知道自己想要什么功能,批处理能否实现(GUI界面当然不是命令行程序所擅长的),涉及到複雜結構體的填充,多個指針的調用,及上下函數傳遞參數非常複雜的情況不宜用dllcall。其实通过调用一两个API函数实现的功能最适合dllcall了,一些例子就在下面。

dllcall使用教程看这里

dllcall,call functions in DLL. 用法: dllcall {函数名} [参数]… {动态链接库名称}

Usage: dllcall {function name} [parameter(s)]... {DLL name}
Examples:
dllcall Sleep,1000,kernel32.dll
dllcall MessageBoxA,,"Hello world!",Prompt,1,user32.dll
dllcall MessageBoxW,,"%uAdd prefix %u indicate unicode s ",%ua,1,user32.dll
dllcall ShellExecuteA,,,"http://www.google.com",,,,shell32.dll
dllcall 61,,,,,,shell32.dll
dllcall 61,,,,%uRun,"%u Only Demo",,shell32.dll
dllcall SendMessageA,0x0FFFF,0x00112,0x0F170,1,user32.dll
the display is going to low power.
dllcall SHChangeNotify,0x8000000,0x1000,,,shell32.dll
refresh desktop items.

使用%u前缀指明后面跟的是unicode字符串,原因是所有与字符有关的API都有ansi及unicode两种版本,其函数后面有A及W字符,如MessageBoxA和MessageBoxW,而系统默认的字符编码是unicode,所以在给一些用序号导出的API传递字符串参数时只能在调用时实现编码的转换,shell32.dll中序号为61的函数就是这种情况。

用%o使用一个输出型参数,每次调用只能有一个%o出现,%o输出的结果在返回值之后,如dllcall GetWindowsDirectoryA,%o,4096,kernel32.dll ,输出结果为 10 C:\WINDOWS 。%od将输出转换为一整数 调用函数的返回值回显为一个十进制的数。

2009-12-29 更新:

考虑再三,还是加上了多个函数的调用,如 dllcall OpenClipboard,,user32.dll;GetClipboardData,1,user32.dll\*;CloseClipboard,user32.dll- 这样的格式,”*”指明返回值是一个指向字符串的指针。

文档及源码整理得不太好,故文档不再放出,源码暂不放出。

每个DLL导出的函数可用 gie 获取,在这里下载,如用 gie -e shell32.dll 得到 shell32.dll 的所有导出函数,API的调用说明参见MSDN,也可在这里找到相关的资源。

1.0.3.0版本加入功能说明: 加入%r参数说明符,加入符号”;”、”*”、”-“,增加了多个函数的调用。 %r用来引用一次dllcall使用中上一个函数的返回值; “;”用来分隔调用的函数,在函数中使用”;”用”;”转义,”*”及”-“放在一个函数调用语句的后面,”*”用来指示函数的返回值是一个字符串的地址,”-“用来指示函数的返回值不再打印出来。例如:

dllcall OpenClipboard,,user32.dll;GetClipboardData,1,user32.dll\*;CloseClipboard,user32.dll-

这个语句中有3个函数,第一个函数打开剪切板,第二个函数取得其中的数据,第三个函数关闭了剪切板。

2010-6-21 更新:

1.0.3.3版本加入功能说明: 加入%p参数说明符。 %p指出这个参数是一个指针,其后可跟一个十进制或十六进制的数对其赋初值。例如:

dllcall IsNetworkAlive,%p0x1,sensapi.dll

这个语句中传入IsNetworkAlive的参数是一个指针(即地址值),这个地址中的内容是1 。

2010-7-17 更新:

1.0.3.4版本加入功能说明: 加入”-r”选项。 由于Windows的命令解释器cmd.exe的for命令中使用dllcall会出错,所以”-r”选项的作用就是给出一个替代”,”的字符,如:

for /f %i in ('dllcall -r# MessageBoxA##"test"#p#0x24#user32.dll') do set return=%i

2013-3-17 更新:

修正了文檔及示例的多個錯誤。

TODO: 抽點時間,完善一下,包括文檔,多做幾個示例。

2013-3-30 更新:

源代碼中添加一些簡單的註釋並釋出。

更新到1.0.3.5,並將調用的最後一個函數的返回值作爲dllcall的退出碼。

源码及可執行文件下載: http://files.vinoca.org

示例1: 对 notepad 操作

@echo off
start "" notepad.exe
dllcall Sleep,700,kernel32.dll-
dllcall FindWindowA,"Notepad",,user32.dll-
set hPad=%errorlevel%
dllcall ShowWindow,%hPad%,3,user32.dll-
dllcall FindWindowExA,%hPad%,,"Edit",,user32.dll-
set hEdit=%errorlevel%
call:putchar 0x4F
call:putchar 0x6E
call:putchar 0x6C
call:putchar 0x79
call:putchar 0x2C 900
call:putchar 0x08
call:putchar 0x20
call:putchar 0x44
call:putchar 0x45
call:putchar 0x4D
call:putchar 0x4F
call:putchar 0x2E
call:sleep 1500
rem 发送 WM_QUIT(0x12) 消息到notepad的主窗体,关闭notpead .
dllcall PostMessageA,%hPad%,0x12,,,user32.dll-
pause
 
:putchar <char>
setlocal
set arg2=%2&&if "%2" == "" set arg2=300
rem 发送WM_CHAR(0x102)消息到notepad的子窗体edit ,输入一个字符,wParam 参数为字符
dllcall SendMessageA,%hEdit%,0x102,%1,,user32.dll-
call:sleep %arg2%
exit /b
 
:sleep <ms>
dllcall Sleep,%1,kernel32.dll-
exit /b

示例2: 关闭显示器 。

@echo off
rem invoke SendMessage,HWND_BROADCAST,WM_SYSCOMMAND,SC_MONITORPOWER,1
rem 使显示器处于低功耗状态
rem dllcall SendMessageA,0x0FFFF,0x00112,0x0F170,1,user32.dll
rem invoke SendMessage,HWND_BROADCAST,WM_SYSCOMMAND,SC_MONITORPOWER,2
rem 关闭显示器
dllcall SendMessageA,0x0FFFF,0x00112,0x0F170,2,user32.dll

示例3: 修改“开始”按钮的文字(WinXP)

@echo off
for /f %%i in ('dllcall -r# FindWindowA#"Shell_TrayWnd"##user32.dll') do set hWindow=%%i
for /f %%i in ('dllcall -r# FindWindowExA#%hWindow%##"Button"##user32.dll') do set hWindow=%%i
rem 发送WM_SETTEXT 消息到子窗体
dllcall SendMessageA,%hWindow%,0x0C,,"傳送",user32.dll-
exit

示例4: 隐藏批处理窗体及播放wav格式音频文件

@echo off
set cmdTitle=cmd%random%
title %cmdTitle%
for /f %%i in ('dllcall -r# FindWindowA##%cmdTitle%#user32.dll') do set hCmd=%%i
rem 隐藏窗口1秒
dllcall ShowWindow,%hCmd%,,user32.dll-
dllcall Sleep,1000,kernel32.dll-
dllcall ShowWindow,%hCmd%,1,user32.dll-
rem 播放 WAV 格式声音
dllcall PlaySoundA,"type.wav",,0x20000,winmm.dll-
pause

示例4: 打开关闭光驱仓门

@echo off
dllcall mciSendStringA,"open cdaudio",,,,winmm.dll-
::打开光驱仓门
dllcall mciSendStringA,"set cdaudio door open",,,,winmm.dll-
::关闭光驱仓门
::dllcall mciSendStringA,"set cdaudio door closed",,,,winmm.dll-
dllcall mciSendStringA,"close cdaudio",,,,winmm.dll-
pause

示例5: 打开可移动存储设备

@echo off
 
set n=1
:loop
if %n%==12 goto endloop
for /f "tokens=%n% delims=," %%i in ("C:,D:,E:,F:,E:,H:,I:,J:,K:,L:,M:,N:") do set driveString=%%i
for /f %%i in ('dllcall -r# GetDriveTypeA#%driveString%#kernel32.dll') do set driveType=%%i
if %driveType%==2 start "" "%driveString%"
set /a n+=1
goto loop
:endloop
pause

示例6: MessageBox 演示

@echo off
 
set strPrompt=演示
::dllcall Beep,5000,50,kernel32.dll
::dllcall MessageBeep,0x20,user32.dll
dllcall MessageBoxA,,"你好",%strPrompt%,,user32.dll-
for /f %%i in ('dllcall -r# MessageBoxA##"喜欢用批处理吗?"#%strPrompt%#0x24#user32.dll') do set return=%%i
if %return%==6 dllcall MessageBoxA,,"我也很喜欢哦。",%strPrompt%,0x30,user32.dll-
if %return%==7 dllcall MessageBoxA,,"Oh ,No!",%strPrompt%,0x10,user32.dll-
pause

示例7: 命令行显示剪切板中文字数据

dllcall OpenClipboard,,user32.dll;GetClipboardData,1,user32.dll\*;CloseClipboard,user32.dll-

示例8: 设置屏幕字体的边缘平滑

dllcall SystemParametersInfoA,75,1,0,3,user32.dll-
dllcall SystemParametersInfoA,8203,0,2,3,user32.dll-
dllcall SystemParametersInfoA,8211,0,1,3,user32.dll-
dllcall SystemParametersInfoA,8205,0,1000,3,user32.dll-
dllcall RedrawWindow,0,0,0,1927,user32.dll-

示例9: 测试网络是否可用

dllcall IsNetworkAlive,%p0x1,sensapi.dll
:: 返回1可用,返回0不可用。

示例10: 模拟键盘按键

start "" notepad.exe
for /f %%i in ('dllcall -r# FindWindowA#"Notepad"##user32.dll') do set hPad=%%i
dllcall ShowWindow,%hPad%,3,user32.dll-
dllcall Sleep,1000,kernel32.dll-
::发送Alt+H 按键
dllcall PostMessageA,%hPad%,0x104,0x48,0x20210001,user32.dll-
dllcall Sleep,1000,kernel32.dll-
::发送A 按键
dllcall PostMessageA,%hPad%,0x100,0x41,0,user32.dll-
dllcall Sleep,2000,kernel32.dll-
dllcall PostMessageA,%hPad%,0x12,,,user32.dll-
pause

示例11: plink cmd命令提示符窗口隐藏运行

@echo off
set cmdTitle=plink
title %cmdTitle%
for /f %%i in ('dllcall -r# FindWindowA##%cmdTitle%#user32.dll') do set hCmd=%%i
dllcall ShowWindow,%hCmd%,,user32.dll-
:: 443 远程ssh端口, 3390 本地socks端口
plink -v -N ssh.com -l root -pw 123456 -P 443 -D 3390